﻿using Concentus;
using Concentus.Oggfile;
using Concentus.Structs;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Org.BouncyCastle.Utilities.Encoders;
using System.Text;
using System.Threading.Channels;

namespace MyTRCP.Common.Services
{
    public class AudioRecordService : BackgroundService
    {
        private readonly ILogger<AudioRecordService> _logger;
        private readonly BlockingQueue<byte[]> _pcmQueue;
        public bool NowRecording { get; private set; }
        public AudioTypeEnum NowRecordingAudioType { get; private set; }
        public DateTime NowRecordingBeginTime { get; private set; }
        public DateTime NowRecordingEndTime { get; private set; }
        public string? NowRecordingMemo { get; set; }
        public List<short> NowRecordingCollectingDatas { get; private set; }
        public BlockingQueue<byte[]> PcmQueue
        {
            get
            {
                return _pcmQueue;
            }
        }
        public AudioRecordService(ILogger<AudioRecordService> logger)
        {
            _logger = logger;
            _pcmQueue = new BlockingQueue<byte[]>();
            NowRecordingCollectingDatas = new List<short>();
        }
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            await Task.Run(() => ThreadMethod(stoppingToken), stoppingToken);
        }
        public override Task StartAsync(CancellationToken cancellationToken)
        {
            _logger.LogDebug("startasync called");
            return base.StartAsync(cancellationToken);
        }
        public override Task StopAsync(CancellationToken cancellationToken)
        {
            _pcmQueue.CancelTake();
            return base.StopAsync(cancellationToken);
        }
        private void ThreadMethod(CancellationToken stoppingToken)
        {
            int opusSampleRate = 24000;
            try
            {
                using IOpusEncoder oe = OpusCodecFactory.CreateEncoder(opusSampleRate, 1);
                oe.Bitrate = 24000;
                using IResampler rs = ResamplerFactory.CreateResampler(1, 32000, opusSampleRate, 10);
                short[] rsInBuffer = new short[1000];
                Span<short> rsInSpan = rsInBuffer.AsSpan();
                short[] rsOutBuffer = new short[1000];
                Span<short> rsOutSpan = rsOutBuffer.AsSpan();
                while (!stoppingToken.IsCancellationRequested)
                {
                    try
                    {
                        byte[] data = _pcmQueue.Take();
                        if (data.Length == 1)
                        {
                            NowRecording = true;
                            NowRecordingBeginTime = DateTime.Now;
                            switch (data[0])
                            {
                                case 0:
                                    NowRecordingAudioType = AudioTypeEnum.DevRxAudio;
                                    break;
                                case 9:
                                    NowRecordingAudioType = AudioTypeEnum.DevTxAudio;
                                    break;
                                case 255:
                                    NowRecordingAudioType = AudioTypeEnum.ClientTxAudio;
                                    break;
                            }
                        }
                        else if (data.Length == 0)
                        {
                            NowRecordingEndTime = DateTime.Now;
                            SaveRecord(oe, NowRecordingCollectingDatas.ToArray(), NowRecordingBeginTime, NowRecordingEndTime, NowRecordingAudioType, NowRecordingMemo);
                            NowRecording = false;
                            NowRecordingBeginTime = DateTime.MinValue;
                            NowRecordingEndTime = DateTime.MinValue;
                            NowRecordingAudioType = AudioTypeEnum.Unknown;
                            NowRecordingMemo = null;
                            NowRecordingCollectingDatas.Clear();
                        }
                        else
                        {
                            if (NowRecording)
                            {
                                int inLen = Bytes2Shorts(data, rsInSpan);
                                int outLen = rsOutSpan.Length;
                                rs.ProcessInterleaved(rsInSpan, ref inLen, rsOutSpan, ref outLen);
                                NowRecordingCollectingDatas.AddRange(rsOutSpan.Slice(0, outLen));
                            }
                        }
                    }
                    catch (OperationCanceledException)
                    {
                        _logger.LogInformation("录音线程收到取消信号，退出");
                        break;
                    }
                    catch (Exception e)
                    {
                        _logger.LogError(e, $"录音线程循环内出现异常：{e.Message}");
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, $"录音线程异常：{ex.Message}");
            }
        }
        private int Bytes2Shorts(byte[] inBytes, Span<short> outSpan, bool bigEndian = false)
        {
            int len = inBytes.Length / 2;
            for (int i = 0; i < len; i++)
            {
                if (bigEndian)
                {
                    outSpan[i] = (short)(inBytes[i * 2] << 8 | inBytes[i * 2 + 1]);
                }
                else
                {
                    outSpan[i] = (short)(inBytes[i * 2] | inBytes[i * 2 + 1] << 8);
                }
            }
            return len;
        }
        private void SaveRecord(IOpusEncoder oe, short[] pcmData, DateTime beginDT, DateTime endDT, AudioTypeEnum at, string? memo = null)
        {
            if (pcmData.Length < oe.SampleRate * 0.8)
            {
                _logger.LogInformation($"收到小于0.8s录音，开始于{beginDT:O}，结束于{endDT:O}，类型为{at}，备注为{memo}，将丢弃。");
            }
            else
            {
                _logger.LogDebug($"收到大于等于0.8s录音，开始于{beginDT:O}，结束于{endDT:O}，类型为{at}，备注为{memo}，将保存。");
                string audioTypeStr = "";
                switch (at)
                {
                    case AudioTypeEnum.DevRxAudio:
                        audioTypeStr = "设备接收";
                        break;
                    case AudioTypeEnum.DevTxAudio:
                        audioTypeStr = "设备发射";
                        break;
                    case AudioTypeEnum.ClientTxAudio:
                        audioTypeStr = "远控发射";
                        break;
                }
                string filename = $"{beginDT:yyyyMMddHHmmssffff}-{endDT:yyyyMMddHHmmssffff}-{audioTypeStr}";
                if (!string.IsNullOrEmpty(memo))
                {
                    filename += $"-{memo}";
                }
                string path1 = "records";
                if (!Directory.Exists(path1))
                    Directory.CreateDirectory(path1);
                string path2 = path1 + $"\\{beginDT:yyyy-MM-dd}";
                if (!Directory.Exists(path2))
                    Directory.CreateDirectory(path2);
                string path3 = path2 + $"\\{filename}.opus";
                OpusTags tags = new OpusTags();
                tags.Fields[OpusTagName.Title] = filename;
                using FileStream fs = new FileStream(path3, FileMode.Create);
                OpusOggWriteStream ws = new OpusOggWriteStream(oe, fs, tags);
                ws.WriteSamples(pcmData, 0, pcmData.Length);
                ws.Finish();
                _logger.LogDebug($"已保存录音：{path3}");
            }
        }
    }
}
